using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using TagTool.Devices;
using TagTool.Devices.DataStructures;
using TagTool.GraphicTypes;

namespace TagTool.Devices.Tablet
{
    /// <summary> Represents a tablet's context </summary>
    public sealed class WinTabletContext : ITabletContext
    {
        #region Exposed events

        /// <summary> For handling tablet's cursor events </summary>
        public event CursorDeviceEventHandler CursorDeviceEvent;

        #endregion

        #region Members

        /// <summary> A bitmask to specify which information is requested from the device </summary>
        /// <remarks>
        /// The bitmask defines the layout-structure of packets sent from the device
        /// </remarks>
        private uint defaultPacketData;
        /// <summary> A contextHandle to a logial context </summary>
        private IntPtr contextHandle = IntPtr.Zero;
        private IntPtr windowHandle;

        /// <summary> The default logical context </summary>
        private WTContext context;

        /// <summary> The context name </summary>
        private string name;
        /// <summary> The device id </summary>
        private uint deviceId;
        /// Current cursor's location
        private PointD location;
        /// Current cursor's normal pressure 
        private uint normalPressure;
        /// Current cursor's tangent pressure 
        private uint tangentPressure;
        /// <summary> Current cursor's orientation </summary>
        private TagTool.Devices.DataStructures.Orientation orientation;
        /// Current cursor's pressed buttons
        private uint buttons;

        private uint oldX = 0;
        private uint oldY = 0;
        private uint oldZ = 0;
        
        double aspect;
        
        /// <summary> Current proximity </summary>
        private bool isInContextProximity;
        private bool isInHardwareProximity;

        /// <summary> Origin and size of the working space </summary>
        private DeviceResolution resolution;
        private DeviceResolution orientationResolution;

        /// <summary> Argument sent on events </summary>
        private CursorDeviceEventArgs cursorArgs;

        /// <summary> Packet for receiving data from device </summary>
        private Packet packet;
        /// <summary> Dummy form to receive messages from the tablet driver. </summary>
        private static WinTabletForm form = new WinTabletForm();

        #endregion

        /// <summary> Standard constructor. Initializes tracing and the window </summary>
        public WinTabletContext(uint deviceId)
        {
            windowHandle = form.Handle;
            
            this.deviceId = deviceId;
            location = new PointD();
            
            resolution = GetResolution();
            orientation = new TagTool.Devices.DataStructures.Orientation();
       		//orientationResolution = GetOrientationResolution();                    
            
            context = GetDefaultContext();
            cursorArgs = new CursorDeviceEventArgs();
            cursorArgs.DeviceId = deviceId;
            packet = new Packet();

        }

        /// <summary> Destructor. Makes sure to close the tablet </summary>
        ~WinTabletContext()
        {
            form.Dispose();
            Disconnect();
        }

        #region Properties

        /// <summary> Property Name </summary>
        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                name = value;
            }
        }

        /// <summary> Property ContextHandle </summary>
        public IntPtr ContextHandle {
        	get
        	{
        		return contextHandle;
        	}
        }

        /// <summary> Property DeviceId </summary>
        public int DeviceId
        {
            get
            {
                return (int)deviceId;
            }
        }

        /// <summary> Property X </summary>
        public double X
        {
            get { return location.X; }
        }

        /// <summary> Property Y </summary>
        public double Y
        {
            get { return location.Y; }
        }

        /// <summary> Property Z </summary>
        public double Z
        {
            get { return location.Z; }
        }

        /// <summary> Property Orientation </summary>
        public TagTool.Devices.DataStructures.Orientation Orientation
        {
            get { return orientation; }
        }

        /// <summary> Property Location </summary>
        public PointD Location
        {
            get { return location; }
        }

        /// <summary> Property NormalPressure </summary>
        public uint NormalPressure
        {
            get { return normalPressure; }
        }

        /// <summary> Property TangentPressure </summary>
        public uint TangentPressure
        {
            get { return tangentPressure; }
        }

        /// <summary> Property Buttons </summary>
        public uint Buttons
        {
            get { return buttons; }
        }

        /// <summary> Property Resolution </summary>
        public DeviceResolution Resolution
        {
            get { return resolution; }
        }

        /// <summary> Property OrientationResolution </summary>
        public DeviceResolution OrientationResolution
        {
            get { return orientationResolution; }
        }

        /// <summary> Property Available </summary>
        public bool Available
        {
            get
            {
                IntPtr dummy = IntPtr.Zero;
                return (WTFunction.WTInfoIntPtr(0, 0, ref dummy) != 0);
            }
        }

        /// <summary> Property IsInContextProximity </summary>
        public bool IsInContextProximity
        {
            get { return isInContextProximity; }
        }

        /// <summary> Property IsInHardwareProximity </summary>
        public bool IsInHardwareProximity
        {
            get { return isInHardwareProximity; }
        }


        /// <summary> Property Enabled </summary>
        /// <remarks>
        /// Calls WTGet() and inpects the status of the context,
        /// which is zero if the device is disabled.
        /// </remarks>
        public bool Enabled
        {
            get
            {
                bool enabled = false;
                if (contextHandle != IntPtr.Zero)
                {
                    if (WTFunction.WTGet(contextHandle, ref context))
                    {
                        if (context.Status != 0)
                        {
                            enabled = true;
                        }
                    }
                }
                return enabled;
            }
        }

        #endregion

        #region Event handlers

        /// <summary> Handle packet-message </summary>
        /// <param name="wParam"> Serial number of the packet </param>
        /// <param name="lParam"> Handle of context </param>
        /// <returns> True if data was written to packet </returns>
        public void OnPacket(IntPtr wParam, IntPtr lParam)
        {
            if (WTFunction.WTPacket(lParam, (uint)wParam.ToInt32(), ref packet))
            {
            	bool sendevent = false;
            	if(Math.Abs(packet.X - oldX) > 50)
            	{
            		sendevent = true;
            		oldX = packet.X;
            		location.X = cursorArgs.Location.X = Math.Round(((double)packet.X - resolution.X.Maximum / 2) / resolution.X.Maximum * 200, 4);
            		location.Y = cursorArgs.Location.Y = Math.Round( (((resolution.Y.Maximum / 2) - (double)packet.Y) / resolution.Y.Maximum * 200)  * aspect, 4);
            	}
            	if(Math.Abs(packet.Y - oldY) > 50)
            	{
            		sendevent = true;
            		oldY = packet.Y;
            		location.Y = cursorArgs.Location.Y = Math.Round( (((resolution.Y.Maximum / 2) - (double)packet.Y) / resolution.Y.Maximum * 200)  * aspect, 4);
            		location.X = cursorArgs.Location.X = Math.Round(((double)packet.X - resolution.X.Maximum / 2) / resolution.X.Maximum * 200, 4);
            	}
            	if(Math.Abs(packet.Z - oldZ) > 5)
            	{
            		sendevent = true;
            		oldZ = packet.Z;
            		location.Z = cursorArgs.Location.Z = packet.Z;
            	}
            	
                cursorArgs.Context = (uint)lParam;
                buttons = cursorArgs.Buttons = packet.Button;
//                location.X = cursorArgs.Location.X = ((double)packet.X - resolution.X.Maximum / 2) / resolution.X.Maximum * 200;
//                location.Y = cursorArgs.Location.Y = ((resolution.Y.Maximum / 2) - (double)packet.Y) / resolution.Y.Maximum * 200;
//                location.Z = cursorArgs.Location.Z = packet.Z;
                normalPressure = cursorArgs.NormalPressure = packet.NormalPressure;
                tangentPressure = cursorArgs.TangentPressure = packet.TangentPressure;
                orientation.Altitude = packet.Orientation.Altitude;
                orientation.Azimuth = packet.Orientation.Azimuth;
                orientation.Twist = packet.Orientation.Twist;
                cursorArgs.Orientation.Altitude = orientation.Altitude;
                cursorArgs.Orientation.Azimuth = orientation.Azimuth;
                cursorArgs.Orientation.Twist = orientation.Twist;
                if (CursorDeviceEvent != null)
                {
                	if(sendevent == true)
                	{
                    	CursorDeviceEvent(this, cursorArgs);
                	}
                }
            }
        }

        public void OnContextOpen(IntPtr wParam, IntPtr lParam)
        {
        }

        public void OnContextClose(IntPtr wParam, IntPtr lParam)
        {
        }

        public void OnContextUpdate(IntPtr wParam, IntPtr lParam)
        {
        }

        public void OnContextOverlap(IntPtr wParam, IntPtr lParam)
        {
        }

        public void OnProximity(IntPtr wParam, IntPtr lParam)
        {
            isInContextProximity = (((uint)lParam) & 0xffff0000) != 0;
            isInHardwareProximity = (((uint)lParam) & 0x0000ffff) != 0;
        }

        public void OnInfoChange(IntPtr wParam, IntPtr lParam)
        {
        }

        public void OnCursorChange(IntPtr wParam, IntPtr lParam)
        {
        }

        #endregion Event handlers

        #region Methods

        public const double Inch = 2.54;

        public double CentimeterToInch(double centimeter)
        {
            return Inch * centimeter;
        }

        public double InchToCentimeter(double inch)
        {
            return inch / Inch;
        }

        /// <summary> Get the default packet data </summary>
        /// <returns> An uint with all packet bits set </returns>
        private uint GetDefaultPacketData()
        {
            return (uint)(WTPkt.Context |
                                WTPkt.Status |
                                WTPkt.Time |
                                WTPkt.Changed |
                                WTPkt.SerialNumber |
                                WTPkt.Cursor |
                                WTPkt.Buttons |
                                WTPkt.X |
                                WTPkt.Y |
                                WTPkt.Z |
                                WTPkt.NormalPressure |
                                WTPkt.TangentPressure |
                                WTPkt.Orientation |
                                WTPkt.Rotation);
        }

        /// <summary> Get the default context for a tablet </summary>
        /// <returns> The default logical context </returns>
        private WTContext GetDefaultContext()
        {
        	defaultPacketData = GetDefaultPacketData();
        	WTContext context = new WTContext();
			WTFunction.WTInfo((uint)(WTInfoCategory.DDCTXS) + deviceId, 0, ref context);
            context.PktData = defaultPacketData;
            context.MoveMask = defaultPacketData;
            context.Options = (uint)(WTContextOptions.Messages);
            context.Device = deviceId;
            
            return context;
        }

        /// <summary> Opens default context to a tablet device </summary>
        public void Connect()
        {
            if (contextHandle != IntPtr.Zero)
            {
                WTFunction.WTClose(contextHandle);
                context = GetDefaultContext();
            }
            
            contextHandle = WTFunction.WTOpen(windowHandle, ref context, true);
           
            if (contextHandle != IntPtr.Zero)
            {
                bool enabled = WTFunction.WTEnable(contextHandle, true);
                bool success = WTFunction.WTGet(contextHandle, ref context);
            }
        }

        /// <summary> Closes the context and sets the contextHandle to it to zero </summary>
        public void Disconnect()
        {
            if (contextHandle != IntPtr.Zero)
            {
                WTFunction.WTClose(contextHandle);
                contextHandle = IntPtr.Zero;
            }
        }

        /// <summary> Enables the device </summary>
        /// <returns> True if device was successfully enabled </returns>
        public bool Enable()
        {
           return WTFunction.WTEnable(contextHandle, true);;
        }

        /// <summary> Disables the device </summary>
        /// <returns> True if device was successfully disabled </returns>
        public bool Disable()
        {
            return WTFunction.WTEnable(contextHandle, false);
        }

        /// <summary> Get the resolution of a device </summary>
        /// <param name="deviceId"> The device id </param>
        /// <returns> The device's resolution </returns>
        private DeviceResolution GetResolution()
        {
            WTAxis x = new WTAxis();
            WTAxis y = new WTAxis();
            WTAxis z = new WTAxis();
            
            uint category = (uint)WTInfoCategory.Devices + deviceId;
            WTFunction.WTInfoAxis(category, (uint)WTIDevices.X, ref x);
            WTFunction.WTInfoAxis(category, (uint)WTIDevices.Y, ref y);
            WTFunction.WTInfoAxis(category, (uint)WTIDevices.Z, ref z);
			
            aspect = (double)y.Maximum / (double)x.Maximum;
            return new DeviceResolution(
                new Axis(x.Minimum, x.Maximum, (AxisUnit)x.Units, x.Resolution),
                new Axis(y.Minimum, y.Maximum, (AxisUnit)y.Units, y.Resolution),
                new Axis(z.Minimum, z.Maximum, (AxisUnit)z.Units, z.Resolution)
            );
        }

        private DeviceResolution GetOrientationResolution()
        {
        	WTAxis altResolution = new WTAxis();
            WTAxis aziResolution = new WTAxis();
            WTAxis twistResolution = new WTAxis();
         	uint category = (uint)WTInfoCategory.Devices + deviceId;
         	
            WTFunction.WTInfoAxis(category, (uint)WTIDevices.Orientation, ref altResolution);
            WTFunction.WTInfoAxis(category, (uint)WTIDevices.Orientation, ref aziResolution);
            WTFunction.WTInfoAxis(category, (uint)WTIDevices.Orientation, ref twistResolution);

			return new DeviceResolution(
            	new Axis(altResolution.Minimum, altResolution.Maximum, (AxisUnit)altResolution.Units, altResolution.Resolution),
                new Axis(aziResolution.Minimum, aziResolution.Maximum, (AxisUnit)aziResolution.Units, aziResolution.Resolution),
                new Axis(twistResolution.Minimum, twistResolution.Maximum, (AxisUnit)twistResolution.Units, twistResolution.Resolution)
			);
        }
        
        private void GetDeviceMargins(ref int dvc_xmargin, ref int dvc_ymargin, ref int dvc_zmargin)
        {
            uint category = (uint)WTInfoCategory.Devices + deviceId;

            WTFunction.WTInfoInt(category, (uint)WTIDevices.XMargin, ref dvc_xmargin);
            WTFunction.WTInfoInt(category, (uint)WTIDevices.YMargin, ref dvc_ymargin);
            WTFunction.WTInfoInt(category, (uint)WTIDevices.ZMargin, ref dvc_zmargin);
        }

        /*
        public unsafe void GetInterfaceInfo()
        {
            char[] pString = new char[40];
            //string* s = Marshal.PtrToStringAnsi(pString);
            string dvc_name = null;
            IntPtr ptrName = Marshal.StringToBSTR(dvc_name);
            WTInfoIntPtr(WTInfoCategory.Devices, (uint)WTIDevices.Name, ref ptrName);
            WTInfoUInt(WTInfoCategory.Devices, (uint)WTIDevices.Hardware, ref dvc_hardware);
            WTInfoUInt(WTInfoCategory.Devices, (uint)WTIDevices.NCursorTypes, ref dvc_ncsrtypes);
            WTInfoUInt(WTInfoCategory.Devices, (uint)WTIDevices.FirstCursor, ref dvc_firstcsr);
            WTInfoUInt(WTInfoCategory.Devices, (uint)WTIDevices.PacketRate, ref dvc_pktrate);
            WTInfoUInt(WTInfoCategory.Devices, (uint)WTIDevices.PacketData, ref dvc_pktdata);
            WTInfoUInt(WTInfoCategory.Devices, (uint)WTIDevices.PacketMode, ref dvc_pktmode);
            WTInfoUInt(WTInfoCategory.Devices, (uint)WTIDevices.CursorData, ref dvc_csrdata);
        	

//            string s = Marshal.PtrToStringAnsi(ptrName, 40);
//        	Trace.WriteLine(string.Format("Name: {0}", dvc_name));
            Trace.WriteLine(String.Format("DVC_HARDWARE {0} DVC_NCSRTYPES {1} DVC_FIRSTCSR {2} DVC_PKTRATE {3} DVC_PKTDATA {4} DVC_PKTMODE  {5} DVC_CSRDATA {6} DVC_XMARGIN {7} DVC_YMARGIN {8} DVC_ZMARGIN {9}",
                                         dvc_hardware,
                                         dvc_ncsrtypes,
                                         dvc_firstcsr,
                                         dvc_pktrate,
                                         dvc_pktdata,
                                         dvc_pktmode,
                                         dvc_csrdata,
                                         dvc_xmargin,
                                         dvc_ymargin,
                                         dvc_zmargin));
        	                        
            if (orientationResolution == null)
            {
                orientationResolution = new DeviceResolution(new Axis(altResolution.Minimum, altResolution.Maximum, AxisUnit.None, 0.0), new Axis(aziResolution.Minimum, aziResolution.Maximum, AxisUnit.None, 0.0), new Axis(twistResolution.Minimum, twistResolution.Maximum, AxisUnit.None, 0.0));
            }
        }
        */

        private void WriteTrace(string function, string context)
        {
        }

        #endregion
    }
}
